/*
 *
 * Copyright(c) 2018-2019 chj <1019487871@qq.com>
 * QQ: 1019487871
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "los.h"
#include "../component/fundation/string/los_string.h"

// queue
void
losq_init(struct losq_t *q)
{
    q->head = q->tail = NULL;
}

void
losq_append(struct losq_t *q, struct losq_node_t *n)
{
    n->next = NULL;
    if (NULL == q->tail)
    {
        q->head = q->tail = n;
    }
    else
    {
        q->tail->next = n;
        q->tail = n;
    }
}

void
losq_remove(struct losq_t *q, struct losq_node_t *n)
{
    struct losq_node_t *head = q->head;

    if (head == n)
    {
        q->head = head->next;
        if (NULL == q->head)
        {
            q->tail = NULL;
        }
    }
    else if (head != NULL)
    {
        while (head->next != NULL)
        {
            if (head->next == n)
            {
                head->next = head->next->next;
                if (NULL == head->next)
                {
                    q->tail = head;
                }
                break;
            }
            head = head->next;
        }
    }
}

void
losq_enqueue(struct losq_t *q, struct losq_node_t *n)
{
    struct losq_node_t *tmp = q->head;

    n->next = NULL;
    if (NULL == tmp)
    {
        q->head = q->tail = n;
    }
    else if (tmp->addr >= n->addr)
    {
        n->next = tmp;
        q->head = n;
    }
    else
    {
        while (tmp->next != NULL)
        {
            if (tmp->next->addr >= n->addr)
            {
                n->next = tmp->next;
                tmp->next = n;
                break;
            }
            tmp = tmp->next;
        }
        // insert last
        tmp->next = n;
    }
}

struct losq_node_t*
losq_dequeue(struct losq_t *q)
{
    struct losq_node_t *head = q->head;
    if (q->head != NULL)
    {
        q->head = q->head->next;
        if (NULL == q->head)
        {
            q->tail = NULL;
        }
    }
    return head;
}

static uint32_t
los_fifo_get_next_index(uint32_t pos, uint32_t size)
{
    if (++pos >= size)
    {
        pos = 0;
    }
    return pos;
}

int
los_fifo_init(struct los_fifo_t *fifo)
{
    if (NULL == fifo)
    {
        return -1;
    }

    fifo->head = fifo->tail = 0;
    fifo->wrap = false;
    return 0;
}

uint32_t
los_fifo_get_data_length(struct los_fifo_t *fifo)
{
    uint32_t len = 0;
    if (NULL == fifo)
    {
        return 0;
    }
    if (fifo->head >= fifo->tail)
    {
        len = fifo->head - fifo->tail;
        if(fifo->wrap)
        {
            len += fifo->buffer.size;
        }
        return len;
    }
    else
    {
        return fifo->buffer.size - (fifo->tail - fifo->head);
    }
}

uint32_t
los_fifo_get_avail_length(struct los_fifo_t *fifo)
{
    uint32_t len;

    if (NULL == fifo)
    {
        return 0;
    }
    len = fifo->buffer.size - los_fifo_get_data_length(fifo);
    return len;
}

uint32_t
los_fifo_push8(struct los_fifo_t *fifo, uint8_t data)
{
    if (los_fifo_get_avail_length(fifo) < 1)
    {
        return 0;
    }

    fifo->buffer.buffer[fifo->head] = data;
    fifo->head = los_fifo_get_next_index(fifo->head, fifo->buffer.size);
    return 1;
}

uint8_t
los_fifo_pop8(struct los_fifo_t *fifo)
{
    uint8_t data;

    if (los_fifo_get_data_length(fifo) <= 0)
    {
        return 0;
    }

    data = fifo->buffer.buffer[fifo->tail];
    fifo->tail = los_fifo_get_next_index(fifo->tail, fifo->buffer.size);
    return data;
}

uint32_t
los_fifo_push(struct los_fifo_t *fifo, uint32_t size, uint8_t *data)
{
    uint32_t tmp32;

    if ((NULL == fifo) || (NULL == data))
    {
        return 0;
    }
    if (size > los_fifo_get_avail_length(fifo))
    {
        return 0;
    }

    tmp32 = fifo->buffer.size - fifo->head;
    if (size > tmp32)
    {
        if (data)
        {
            los_memcpy(&fifo->buffer.buffer[fifo->head], &data[0], tmp32);
            los_memcpy(&fifo->buffer.buffer[0], &data[tmp32], size - tmp32);
        }
        fifo->head = size - tmp32;
    }
    else
    {
        if (data)
        {
            los_memcpy(&fifo->buffer.buffer[fifo->head], data, size);
        }
        fifo->head += size;
        if (fifo->head == fifo->buffer.size)
        {
            fifo->wrap = true;
            fifo->head = 0;
        }
    }
    return size;
}

uint32_t
los_fifo_get_rbuf(struct los_fifo_t *fifo, uint8_t **data)
{
    uint32_t tmp32, avail_len = los_fifo_get_data_length(fifo);

    if (NULL == fifo)
    {
        return 0;
    }
    if (data)
    {
        *data = &fifo->buffer.buffer[fifo->tail];
    }
    tmp32 = fifo->buffer.size - fifo->tail;
    return min(tmp32, avail_len);
}

uint32_t
los_fifo_get_wbuf(struct los_fifo_t *fifo, uint8_t **data)
{
    uint32_t tmp32, avail_len = los_fifo_get_avail_length(fifo);

    if (NULL == fifo)
    {
        return 0;
    }
    if (data)
    {
        *data = &fifo->buffer.buffer[fifo->head];
    }
    tmp32 = fifo->buffer.size - fifo->head;
    return min(tmp32, avail_len);
}

uint32_t
los_fifo_peek(struct los_fifo_t *fifo, uint32_t size, uint8_t *data)
{
    uint32_t tmp32;
    uint32_t avail_len = los_fifo_get_data_length(fifo);
    if (NULL == fifo)
    {
        return 0;
    }
    if (size > avail_len)
    {
        size = avail_len;
    }

    tmp32 = fifo->buffer.size - fifo->tail;
    if (data)
    {
        if (size > tmp32)
        {
            los_memcpy(&data[0], &fifo->buffer.buffer[fifo->tail], tmp32);
            los_memcpy(&data[tmp32], &fifo->buffer.buffer[0], size - tmp32);
        }
        else
        {
            los_memcpy(data, &fifo->buffer.buffer[fifo->tail], size);
        }
    }
    return size;
}

uint32_t
los_fifo_pop(struct los_fifo_t *fifo, uint32_t size, uint8_t *data)
{
    uint32_t tmp32;
    uint32_t ret = los_fifo_peek(fifo, size, data);

    if (!ret)
    {
        return 0;
    }

    tmp32 = fifo->buffer.size - fifo->tail;
    if (ret > tmp32)
    {
        fifo->tail = ret - tmp32;
    }
    else
    {
        fifo->tail += ret;
        if (fifo->tail == fifo->buffer.size)
        {
            fifo->tail = 0;
            if(fifo->wrap)
                fifo->wrap = false;
        }
    }
    return ret;
}


